﻿// HSS.Interlink.UI.ContextMenu.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.html)
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2012
// ----------------------------------------------------------------------------
// File:       ContextMenu.cs
// Author:     HSS\gbanta
// Created:    07/16/2011
// Modified:   07/22/2011
// ----------------------------------------------------------------------------
namespace HSS.Interlink.UI
{
	#region Using Directives
	using System;
	using System.Collections.ObjectModel;
	using System.ComponentModel;
	using System.Windows;
	using System.Windows.Controls;
	using System.Windows.Controls.Primitives;
	using System.Windows.Data;
	using System.Windows.Input;
	using System.Windows.Media;
	#endregion

	#region ContextMenu
	/// <summary>
	/// A simple ContextMenu for use with the <see cref="UploadControl"/>.
	/// </summary>
	public sealed class ContextMenu : DependencyObject
	{
		#region Attached Properties

		/// <summary>
		/// Gets the value of the ContextMenu property of the specified object.
		/// </summary>
		/// <param name="obj">Object to query concerning the ContextMenu property.</param>
		/// <returns>Value of the ContextMenu property.</returns>
		public static ContextMenu GetAttached(DependencyObject obj)
		{
			if (null == obj)
				throw new ArgumentNullException("obj");
			return (ContextMenu)obj.GetValue(AttachedProperty);
		}

		/// <summary>
		/// Sets the value of the ContextMenu property of the specified object.
		/// </summary>
		/// <param name="obj">Object to set the property on.</param>
		/// <param name="value">Value to set.</param>
		public static void SetAttached(DependencyObject obj, ContextMenu value)
		{
			if (null == obj)
				throw new ArgumentNullException("obj");
			obj.SetValue(AttachedProperty, value);
		}

		/// <summary>
		/// Identifies the ContextMenu attached property.
		/// </summary>
		public static readonly DependencyProperty AttachedProperty = DependencyProperty.RegisterAttached(
			"Attached",
			typeof(ContextMenu),
			typeof(ContextMenu),
			new PropertyMetadata(null, OnAttachedChanged));

		/// <summary>
		/// Handles changes to the ContextMenu DependencyProperty.
		/// </summary>
		/// <param name="o">DependencyObject that changed.</param>
		/// <param name="e">Event data for the DependencyPropertyChangedEvent.</param>
		private static void OnAttachedChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
		{
			var ele = o as FrameworkElement;
			if (null != ele)
			{
				var oldContextMenu = e.OldValue as ContextMenu;
				if (null != oldContextMenu)
					oldContextMenu.Owner = null;
				var newContextMenu = e.NewValue as ContextMenu;
				if (null != newContextMenu)
					newContextMenu.Owner = ele;
			}
		}
		#endregion

		#region Fields
		Popup popup;
		Grid popupGrid;
		Canvas popupCanvas;
		Border content;
		FrameworkElement rootVisual;
		ContextMenuView contextView;
		static string templateUri = "/HSS.Interlink.UI;component/themes/contextmenutemplates.xaml";
		static bool templateSet;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructor
		/// </summary>
		public ContextMenu()
		{
			CommonConstructor();
		}
		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="owner">The ContextMenu owner.</param>
		public ContextMenu(Control owner)
		{
			if (null == owner)
				throw new ArgumentNullException("owner");
			this.Owner = owner;
			CommonConstructor();
		}
		private static void CommonConstructor()
		{
			if (!templateSet)
			{
				if (null == Application.Current)
					return;
				templateSet = true;
				var rd = new ResourceDictionary();
				rd.Source = new Uri(templateUri, UriKind.RelativeOrAbsolute);
				Application.Current.Resources.MergedDictionaries.Add(rd);
			}
		}
		#endregion

		#region Properties

		#region Opened
		/// <summary>
		/// Gets or sets a value indicating whether the ContextMenu is visible.
		/// </summary>
		[Browsable(false)]
		public bool IsOpen
		{
			get { return (bool)GetValue(IsOpenProperty); }
			private set { SetValue(IsOpenProperty, value); }
		}
		/// <summary>
		/// Identifies the IsOpen dependency property.
		/// </summary>
		public static readonly DependencyProperty IsOpenProperty = DependencyProperty.Register(
			"IsOpen",
			typeof(bool),
			typeof(ContextMenu),
			new PropertyMetadata(false));
		/// <summary>
		/// Occurs when a particular instance of a ContextMenu opens.
		/// </summary>
		public event RoutedEventHandler Opened;
		/// <summary>
		/// Called when the Opened event occurs.
		/// </summary>
		/// <param name="e">Event arguments.</param>
		private void OnOpened(RoutedEventArgs e)
		{
			RoutedEventHandler handler = Opened;
			if (null != handler)
			{
				handler.Invoke(this, e);
			}
		}
		#endregion

		#region Closed
		/// <summary>
		/// Occurs when a particular instance of a ContextMenu closes.
		/// </summary>
		public event RoutedEventHandler Closed;
		/// <summary>
		/// Called when the Closed event occurs.
		/// </summary>
		/// <param name="e">Event arguments.</param>
		private void OnClosed(RoutedEventArgs e)
		{
			RoutedEventHandler handler = Closed;
			if (null != handler)
			{
				handler.Invoke(this, e);
			}
		}
		#endregion

		#region UseRootVisual
		/// <summary>
		/// Gets or sets whether or not to use the Application's RootVisual as the bounding container. Default: True.
		/// </summary>
		[Category("Interlink")]
		[DefaultValue(true)]
		[Description("Gets or sets whether or not to use the Application's RootVisual as the bounding container. Default: True.")]
		public bool UseRootVisual
		{
			get { return (bool)GetValue(UseRootVisualProperty); }
			set { SetValue(UseRootVisualProperty, value); }
		}
		/// <summary>
		/// Using a DependencyProperty as the backing store for UseRootVisual.  This enables animation, styling, binding, etc...
		/// </summary>
		public static readonly DependencyProperty UseRootVisualProperty =
			DependencyProperty.Register("UseRootVisual", typeof(bool), typeof(ContextMenu), new PropertyMetadata(true));
		#endregion

		#region MinimumEnabledCount
		/// <summary>
		/// Gets or sets the minimum number of enabled menu items required for the context menu to be displayed.
		/// </summary>
		/// <remarks>
		/// <para>
		/// By setting this property to a value greater than 0 will essentially disable the context menu if
		/// all the contained menu items cannot execute.
		/// </para>
		/// </remarks>
		[Category("Interlink")]
		[DefaultValue(1)]
		[Description("Gets or sets the minimum number of enabled menu items required for the context menu to be displayed.")]
		public int MinimumEnabledCount
		{
			get { return (int)GetValue(MinimumEnabledCountProperty); }
			set { SetValue(MinimumEnabledCountProperty, value); }
		}
		/// <summary>
		/// Using a DependencyProperty as the backing store for MinimumEnabledCount.  This enables animation, styling, binding, etc...
		/// </summary>
		public static readonly DependencyProperty MinimumEnabledCountProperty =
			DependencyProperty.Register("MinimumEnabledCount", typeof(int), typeof(ContextMenu), new PropertyMetadata(1));
		#endregion

		#region Owner
		/// <summary>
		/// Gets the Owner of the ContextMenu
		/// </summary>
		[Browsable(false)]
		public DependencyObject Owner
		{
			get { return _owner; }
			internal set
			{
				if (null != _owner)
				{

					var ownerFrameworkElement = _owner as FrameworkElement;
					if (null != ownerFrameworkElement)
					{
						ownerFrameworkElement.MouseRightButtonUp -= new MouseButtonEventHandler(Owner_MouseRightButtonUp);
						ownerFrameworkElement.MouseRightButtonDown -= new MouseButtonEventHandler(Owner_MouseRightButtonDown);
					}
				}
				_owner = value;
				if (null != _owner)
				{
					this.rootVisual = Application.Current.RootVisual as FrameworkElement;
					var ownerFrameworkElement = _owner as FrameworkElement;
					if (null != ownerFrameworkElement)
					{
						var view = new ContextMenuView();
						view.SetBinding(FrameworkElement.DataContextProperty, new Binding("DataContext") { Source = this.Owner });
						this.InitializeView(view);

						ownerFrameworkElement.MouseRightButtonUp += new MouseButtonEventHandler(Owner_MouseRightButtonUp);
						ownerFrameworkElement.MouseRightButtonDown += new MouseButtonEventHandler(Owner_MouseRightButtonDown);
					}
				}
			}
		} DependencyObject _owner;
		#endregion

		#region MenuItems
		/// <summary>
		/// Gets the collection of MenuItems.
		/// </summary>
		public ObservableCollection<ContextMenuItem> MenuItems
		{
			get
			{
				if (null == _menuItems)
					_menuItems = new ObservableCollection<ContextMenuItem>();
				return _menuItems;
			}
			private set { _menuItems = value; }
		} ObservableCollection<ContextMenuItem> _menuItems;
		#endregion

		#endregion

		#region Methods
		private void OpenPopup(Point currentMousePosition)
		{
			// HACK: Until I can figure out how to capture the Menu size.
			var width = 160;
			var height = 0;

			var itmHeight = 24;
			var sepHeight = 4;
			var enabledCount = 0;
			foreach (var item in this._menuItems)
			{
				if (item.CanExecute())
					enabledCount++;
				if (item is Separator)
				{
					height += sepHeight;
				}
				else
				{
					height += itmHeight;
				}
			}

			if (enabledCount < this.MinimumEnabledCount)
				return;

			if (null == this.popup)
				this.InitializePopup(currentMousePosition);

			// Clear selection
			this.contextView.SelectedItem = null;
			foreach (var item in this.MenuItems)
			{
				var container = this.contextView.ItemContainerGenerator.ContainerFromItem(item) as ListBoxItem;
				if (null != container)
				{
					container.IsEnabled = item.CanExecute();
					container.ReleaseMouseCapture();
				}
			}

			// Open the Menu
			this.popup.IsOpen = true;
			this.IsOpen = true;
			this.OnOpened(new RoutedEventArgs());

			// Calculate location...
			this.OnResized();
			var ps = this.popupCanvas.TransformToVisual(null).Transform(new Point(0, 0));
			var y = currentMousePosition.Y;
			var x = currentMousePosition.X;

			var w = this.popupCanvas.Margin.Right + ps.X;
			if (w > 0)
			{
				var diff = w - x;
				if (diff < width)
					x = x - width;
			}

			var h = this.popupCanvas.Margin.Bottom + ps.Y;
			if (h > 0)
			{
				var diff = h - y;
				if (diff < height)
					y = y - height;
			}

			// Set menu location
			this.content.Margin = new Thickness(x, y, 0, 0);


			var ownerFrameworkElement = _owner as FrameworkElement;
			if (null != ownerFrameworkElement)
				ownerFrameworkElement.SizeChanged += new SizeChangedEventHandler(Owner_SizeChanged);

		}
		private void InitializeView(ContextMenuView view)
		{
			this.contextView = view;
			this.contextView.ItemsSource = this.MenuItems;
			this.contextView.MouseLeftButtonDown += new MouseButtonEventHandler(contextView_MouseLeftButtonDown);
			this.contextView.MouseRightButtonDown += new MouseButtonEventHandler(contextView_MouseRightButtonDown);
			this.contextView.SelectionChanged += new SelectionChangedEventHandler(contextView_SelectionChanged);
			this.contextView.LostMouseCapture += new MouseEventHandler(contextView_LostMouseCapture);
			this.contextView.LostFocus += new RoutedEventHandler(contextView_LostFocus);

			var contentGrid = new Grid();

			var rect = new System.Windows.Shapes.Rectangle();
			rect.Margin = new Thickness(0, 0, 2, 0);
			rect.Width = 26;
			rect.Fill = new SolidColorBrush(Color.FromArgb(255, 241, 241, 241));
			rect.HorizontalAlignment = HorizontalAlignment.Left;
			contentGrid.Children.Add(rect);

			rect = new System.Windows.Shapes.Rectangle();
			rect.Margin = new Thickness(26, 0, 0, 0);
			rect.Width = 1;
			rect.Fill = new SolidColorBrush(Color.FromArgb(255, 226, 227, 227));
			rect.HorizontalAlignment = HorizontalAlignment.Left;
			contentGrid.Children.Add(rect);

			rect = new System.Windows.Shapes.Rectangle();
			rect.Margin = new Thickness(27, 0, 0, 0);
			rect.Width = 1;
			rect.Fill = new SolidColorBrush(Color.FromArgb(255, 240, 240, 240));
			rect.HorizontalAlignment = HorizontalAlignment.Left;
			contentGrid.Children.Add(rect);

			rect = new System.Windows.Shapes.Rectangle();
			rect.Margin = new Thickness(27, 0, 0, 0);
			rect.Width = 1;
			rect.Fill = new SolidColorBrush(Colors.White);
			rect.HorizontalAlignment = HorizontalAlignment.Left;
			contentGrid.Children.Add(rect);

			contentGrid.Children.Add(this.contextView);

			this.content = new Border()
			{
				HorizontalAlignment = HorizontalAlignment.Left,
				VerticalAlignment = VerticalAlignment.Top,
				CornerRadius = new CornerRadius(2),
				BorderThickness = new Thickness(1),
				BorderBrush = new SolidColorBrush(Colors.Gray),
				Background = new SolidColorBrush(Color.FromArgb(255, 245, 245, 245)),
				Padding = new Thickness(1),
				Child = contentGrid
			};

			var effect = new System.Windows.Media.Effects.DropShadowEffect();
			effect.BlurRadius = 5;
			effect.Color = Colors.Gray;
			this.content.Effect = effect;

		}
		private void InitializePopup(Point currentMousePosition)
		{
			this.content.Margin = new Thickness(currentMousePosition.X, currentMousePosition.Y, 0, 0);

			this.popupGrid = new Grid();
			this.popupCanvas = new Canvas();

			this.popup = new Popup();
			this.popup.Child = this.popupGrid;

			this.popupGrid.Background = new SolidColorBrush(Colors.Transparent);
			this.popupGrid.MouseLeftButtonDown += new MouseButtonEventHandler(popupGrid_MouseLeftButtonDown);
			this.popupGrid.MouseRightButtonDown += new MouseButtonEventHandler(popupGrid_MouseRightButtonDown);
			this.popupGrid.Children.Add(popupCanvas);
			this.popupGrid.Children.Add(this.content);

			this.popupCanvas.Background = new SolidColorBrush(Colors.Transparent);

			this.OnResized();
		}
		private Point OnResized()
		{
			var pt = new Point();

			if (null == this.popup)
				return pt;

			if (!this.UseRootVisual)
			{
				var frameworkOwner = this.Owner as FrameworkElement;
				if (null != frameworkOwner)
				{

					var generalTransform = frameworkOwner.TransformToVisual(null);
					pt = generalTransform.Transform(new Point(0, 0));

					this.popupGrid.Width = this.rootVisual.ActualWidth;
					this.popupGrid.Height = this.rootVisual.ActualHeight;

					this.popupCanvas.Margin = new Thickness(pt.X, pt.Y, frameworkOwner.ActualWidth, frameworkOwner.ActualHeight);
					this.popupCanvas.Width = frameworkOwner.ActualWidth;
					this.popupCanvas.Height = frameworkOwner.ActualHeight;
				}
			}
			else
			{
				this.popupGrid.Width = this.rootVisual.ActualWidth;
				this.popupGrid.Height = this.rootVisual.ActualHeight;

				this.popupCanvas.Margin = new Thickness(pt.X, pt.Y, this.rootVisual.ActualWidth, this.rootVisual.ActualHeight);
				this.popupCanvas.Width = popupGrid.Width;
				this.popupCanvas.Height = popupGrid.Height;
			}

			return pt;
		}
		private void ClosePopup(bool returnFocus = true)
		{
			if (null != this.popup)
			{
				if (this.popup.IsOpen)
				{
					this.popup.IsOpen = false;
					if (returnFocus)
					{
						System.Threading.ThreadPool.QueueUserWorkItem((o) =>
						{
							this.Dispatcher.BeginInvoke(() =>
							{
								if (null != this.Owner && this.Owner is Control)
									((Control)this.Owner).Focus();
							});
						});
					}
				}
			}
			var ownerFrameworkElement = _owner as FrameworkElement;
			if (null != ownerFrameworkElement)
				ownerFrameworkElement.SizeChanged -= new SizeChangedEventHandler(Owner_SizeChanged);
		}
		#endregion

		#region Event Handlers
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void contextView_SelectionChanged(object sender, SelectionChangedEventArgs e)
		{
			if (null != e.AddedItems && e.AddedItems.Count > 0)
			{
				var item = e.AddedItems[0] as ContextMenuItem;
				if (null != item && item.CanExecute())
				{
					try { item.OnClick(); }
					catch { }
					this.ClosePopup();
				}
			}
		}
		private void contextView_LostFocus(object sender, RoutedEventArgs e)
		{
			this.ClosePopup(true);
		}
		private void contextView_LostMouseCapture(object sender, MouseEventArgs e)
		{
			this.ClosePopup(true);
		}
		void contextView_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
			e.Handled = true;
		}
		private void contextView_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
		{
			e.Handled = true;
		}
		void popupGrid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
			e.Handled = true;
			this.ClosePopup(true);
		}
		private void popupGrid_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
		{
			e.Handled = true;
			this.ClosePopup(true);
		}
		private void Owner_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
		{
			e.Handled = true;
		}
		private void Owner_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
		{
			e.Handled = true;
			this.OpenPopup(e.GetPosition(null));
		}
		private void Owner_SizeChanged(object sender, SizeChangedEventArgs e)
		{
			this.OnResized();
		}
		#endregion
	}
	#endregion
}